/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * Copyright (C) 2007 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Web;
using System.Threading;
using System.Collections.Generic;

using Gtk;
using Gdk;
using Glade;
using Pango;

using Anculus.Core;
using Anculus.Gui;

using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Client.GtkGui;
using Galaxium.Protocol;
using Galaxium.Protocol.Gui;
using Galaxium.Gui;
using Galaxium.Gui.GtkGui;

namespace Galaxium.Protocol.Irc.GtkGui
{
	public class IrcChatWidget : BasicChatWidget
	{
		private IrcContactListView _contactList;
		private Label _listLabel;
		
		internal IrcChatWidget (IContainerWindow<Widget> window, IConversation conv) : base (window, conv)
		{
			IrcConversation conversation = conv as IrcConversation;
			IrcSession session = conversation.Session as IrcSession;
			
			if (!conversation.IsPrivateConversation)
			{
				// Channel events that are in a group conversation.
				conversation.ChannelContactInitialJoin += ChannelContactInitialJoin;
				conversation.ChannelContactJoin += ChannelContactJoin;
				conversation.ChannelContactPart += ChannelContactPart;
				conversation.ChannelContactKick += ChannelContactKick;
				conversation.ChannelContactQuit += ChannelContactQuit;
				conversation.ChannelContactKill += ChannelContactKill;
				conversation.ChannelTopicChanged += HandleTopicChanged;
				conversation.ChannelContactModeChanged += ChannelContactModeChanged;
				conversation.ChannelBanned += ChannelBanned;
				conversation.ChannelInviteOnly += ChannelInviteOnly;
				
				// These are specifics that we want to keep track of.
				(conversation.PrimaryContact as IrcChannel).AutoJoinChanged += ChannelAutoJoinChanged;
				(conversation.PrimaryContact as IrcChannel).PersistentChanged += ChannelPersistentChanged;
				(conversation.PrimaryContact as IrcChannel).ChannelModeChanged += ChannelModeChanged;
				(conversation.PrimaryContact as IrcChannel).CannotSend += HandleCannotSend;
			}
			else
			{
				// DCC chats only exist in private conversations.
				conversation.DCCStateChanged += HandleDCCStateChanged;
			}
			
			// These could occur in either private or non-private conversation.
			conversation.EventReceived += HandleEventReceived;
			conversation.MessageReceived += HandleMessageReceived;
			conversation.NoticeReceived += HandleNoticeReceived;
			
			// TODO: These are odd in here...
			session.ContactChanged += SessionContactChanged;
			session.ContactPresenceChanged += SessionContactPresenceChanged;
			session.ContactDisplayNameChanged += SessionContactDisplayNameChanged;
		}
		
		public override void Initialize ()
		{ 
			// You must call this before anything else.
			base.Initialize ();
			
			if (Conversation.IsChannelConversation)
			{
				IrcChannel channel = Conversation.PrimaryContact as IrcChannel;
				
				GenerateChannelLabel (channel.Topic);
			}
			else
			{
				IrcConversation conv = Conversation as IrcConversation;
				
				if (conv.ChatConnection != null && conv.ChatConnection.IsConnected)
				{
					_personalImage.Pixbuf = IconUtility.GetIcon ("galaxium-status-online", IconSizes.Small);
					_personalLabel.Markup = "<span size=\"small\"><i>Direct connection used</i></span>";
				}
				else
				{
					_personalImage.Pixbuf = IconUtility.GetIcon ("galaxium-status-offline", IconSizes.Small);
					_personalLabel.Markup = "<span size=\"small\"><i>Server connection used</i></span>";
				}
			}
			
			_contactList = new IrcContactListView (Conversation);
			_contactList.Manager = new IrcChannelContactTreeManager ();
			_contactList.RowActivated += HandleRowActivated;
			VBox box = new VBox (false, 2);
			box.BorderWidth = 0;
			
			ScrolledWindow scrolledWindow2 = new ScrolledWindow ();
			scrolledWindow2.HscrollbarPolicy = PolicyType.Automatic;
			scrolledWindow2.VscrollbarPolicy = PolicyType.Automatic;
			scrolledWindow2.Add (_contactList);
			scrolledWindow2.ShadowType = ShadowType.In;
			scrolledWindow2.WidthRequest = 125;
			scrolledWindow2.BorderWidth = 0;
			
			_listLabel = new Label ();
			_listLabel.Xalign = 0.5f;
			
			UpdateTotalContacts ();
			
			box.PackStart (_listLabel, false, true, 0);
			box.PackStart (scrolledWindow2, true, true, 0);
			
			this.SetChatWidget (ChatWidgetPositions.RightDisplayPane, box, true, true, 0);
			
			_messageEntry.GrabFocus ();
			_nativeWidget.ShowAll ();
			
			if (Conversation.IsPrivateConversation)
			{
				HideBox (ChatWidgetPositions.RightDisplayPane);
			}
			
			ProcessLogs ();
			Update();
			
			GtkActivityUtility.ClearTypeFromQueue (Conversation.PrimaryContact, ActivityTypes.Message);
			
			_nativeWidget.Show ();
		}

		void HandleRowActivated(object o, RowActivatedArgs args)
		{
			IrcSession session = Session as IrcSession;
			IContact entity = _contactList.GetModelData (args.Path) as IContact;
			
			var conversation = session.ObtainConversation (entity, true);
			WindowUtility<Widget>.Activate(conversation, true);
		}
		
		public override void Destroy ()
		{
			base.Destroy ();
			
			IrcConversation conversation = _conversation as IrcConversation;
			IrcSession session = conversation.Session as IrcSession;
			
			if (!conversation.IsPrivateConversation)
			{
				// Channel events that are in a group conversation.
				conversation.ChannelContactInitialJoin -= ChannelContactInitialJoin;
				conversation.ChannelContactJoin -= ChannelContactJoin;
				conversation.ChannelContactPart -= ChannelContactPart;
				conversation.ChannelContactKick -= ChannelContactKick;
				conversation.ChannelContactQuit -= ChannelContactQuit;
				conversation.ChannelContactKill -= ChannelContactKill;
				conversation.ChannelTopicChanged -= HandleTopicChanged;
				conversation.ChannelContactModeChanged -= ChannelContactModeChanged;
				conversation.ChannelBanned -= ChannelBanned;
				conversation.ChannelInviteOnly -= ChannelInviteOnly;
				
				// These are specifics that we want to keep track of.
				(conversation.PrimaryContact as IrcChannel).AutoJoinChanged -= ChannelAutoJoinChanged;
				(conversation.PrimaryContact as IrcChannel).PersistentChanged -= ChannelPersistentChanged;
				(conversation.PrimaryContact as IrcChannel).ChannelModeChanged -= ChannelModeChanged;
			}
			else
			{
				// DCC chats only exist in private conversations.
				conversation.DCCStateChanged -= HandleDCCStateChanged;
			}
			
			// These could occur in either private or non-private conversation.
			conversation.EventReceived -= HandleEventReceived;
			conversation.MessageReceived -= HandleMessageReceived;
			conversation.NoticeReceived -= HandleNoticeReceived;
			
			// TODO: These are odd in here...
			session.ContactChanged -= SessionContactChanged;
			session.ContactPresenceChanged -= SessionContactPresenceChanged;
			session.ContactDisplayNameChanged -= SessionContactDisplayNameChanged;
		}
		
		public override void SwitchTo ()
		{
			if (Conversation.IsPrivateConversation)
			{
				MenuUtility.FillToolBar ("/Galaxium/Gui/IRC/ContainerWindow/ContactToolbar", new DefaultExtensionContext (this), _window.Toolbar as Toolbar);
				MenuUtility.FillMenuBar ("/Galaxium/Gui/IRC/ContainerWindow/ContactMenu", new DefaultExtensionContext (this), _window.Menubar as MenuBar);
				MenuUtility.FillToolBar ("/Galaxium/Gui/IRC/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
			}
			else
			{
				MenuUtility.FillToolBar ("/Galaxium/Gui/IRC/ContainerWindow/ChannelToolbar", new DefaultExtensionContext (this), _window.Toolbar as Toolbar);
				MenuUtility.FillMenuBar ("/Galaxium/Gui/IRC/ContainerWindow/ChannelMenu", new DefaultExtensionContext (this), _window.Menubar as MenuBar);
				MenuUtility.FillToolBar ("/Galaxium/Gui/IRC/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
			}
			
			Update ();
		}
		
		public override void Update ()
		{
			base.Update();
			
			_leftInputBox.Visible = false;
			_rightInputBox.Visible = false;
		}

		public override void SwitchFrom ()
		{
			// Stuff to do when we leave the widget.
		}
		
		private void UpdateTotalContacts ()
		{
			// Update the total displayed
			if (Conversation.ContactCollection.Count == 1)
				_listLabel.Markup = String.Format ("<span size=\"small\">1 contact</span>", Conversation.ContactCollection.Count);
			else
				_listLabel.Markup = String.Format ("<span size=\"small\">{0} contacts</span>", Conversation.ContactCollection.Count);
			
		}
		
		public override IEntity GetEntity (string uid, string name)
		{
			IContact contact = _conversation.ContactCollection.GetContactByName (name);
			
			if (contact == null)
				contact = new IrcContact (_conversation.Session as IrcSession, _conversation as IrcConversation, uid, name);
			
			return contact;
		}
		
		private void HandleEventReceived(object sender, MessageEventArgs e)
		{
			_message_display.AddMessage (e.Message);
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void HandleMessageReceived(object sender, MessageEventArgs e)
		{
			_message_display.AddMessage (e.Message);
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
			
			SoundSetUtility.Play(Sound.MessageReceived);
		}
		
		private void HandleCannotSend(object sender, ChannelEventArgs e)
		{
			_message_display.AddEvent ("You cannot send messages to this channel at this time.");
		}
		
		private void HandleNoticeReceived(object sender, MessageEventArgs e)
		{
			_message_display.AddMessage (e.Message);
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
			
			SoundSetUtility.Play(Sound.MessageReceived);
		}
		
		private void HandleTopicChanged(object sender, EntityChangeEventArgs<string> args)
		{
			string msg = null;
			
			if (args.Old == null) //the first time the topic is set
				msg = String.Format ("Topic for {0} is '{1}'", Conversation.PrimaryContact.DisplayName, args.New);
			else
				msg = String.Format ("Topic for {0} changed to '{1}'", Conversation.PrimaryContact.DisplayName, args.New);
			
			_message_display.AddEvent (msg);
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
			
			GenerateChannelLabel (args.New);
		}

		void GenerateChannelLabel (string topic)
		{
			IrcChannel channel = Conversation.PrimaryContact as IrcChannel;
			string flags = IrcProtocolHelper.GetChannelModeString(channel.ChannelFlags);
			
			if (String.IsNullOrEmpty (flags))
				flags = "no flags";
			
			if (String.IsNullOrEmpty (topic))
				_personalLabel.Markup = "<span size=\"small\">["+flags+"] <i>No topic has been set</i></span>";
			else
				_personalLabel.Markup = "<span size=\"small\">["+flags+"] <i>"+GLib.Markup.EscapeText(topic)+"</i></span>";
		}
		
		void HandleDCCStateChanged(object sender, ObjectEventArgs e)
		{
			IrcConversation conv = Conversation as IrcConversation;
			
			if (conv.ChatConnection != null && conv.ChatConnection.IsConnected)
			{
				_personalImage.Pixbuf = IconUtility.GetIcon ("galaxium-status-online", IconSizes.Small);
				_personalLabel.Markup = "<span size=\"small\"><i>Direct connection used</i></span>";
			}
			else
			{
				_personalImage.Pixbuf = IconUtility.GetIcon ("galaxium-status-offline", IconSizes.Small);
				_personalLabel.Markup = "<span size=\"small\"><i>Server connection used</i></span>";
			}
			
			SwitchTo ();
		}
		
		[GLib.ConnectBefore	()]
		protected override void MessageEntryKeyRelease (object sender, KeyReleaseEventArgs args)
		{
			// We do not do anything when the user is typing.
		}
		
		[GLib.ConnectBefore ()]
		protected override void MessageEntryKeyPress (object sender, KeyPressEventArgs args)
		{
			EventKey evnt = args.Event;
			
			if ((evnt.State & ModifierType.ControlMask) != 0)
			{ //ctrl
				if (evnt.Key == Gdk.Key.b) { //ctrl-b (bold)
					_messageEntry.AppendText (IrcConstants.Markers.Bold.ToString (), false);
					args.RetVal = true;
				} else if (evnt.Key == Gdk.Key.u) { //ctrl-u (underline)
					_messageEntry.AppendText (IrcConstants.Markers.Underline.ToString (), false);
					args.RetVal = true;
				} else if (evnt.Key == Gdk.Key.r) { //ctrl-r (invert)
					_messageEntry.AppendText (IrcConstants.Markers.Reverse.ToString (), false);
					args.RetVal = true;
				} else if (evnt.Key == Gdk.Key.k) { //ctrl-k (color)
					_messageEntry.AppendText (IrcConstants.Markers.Color.ToString (), false);
					//TODO: popup a dialog to select the colors
					args.RetVal = true;
				}
			}
		}
		
		protected override void MessageEntryTextSubmitted (object sender, SubmitTextEventArgs args)
		{
			string text = args.Text.TrimEnd ();
			_messageEntry.Clear ();
			
			if (text.Length == 0)
				return;
			
			IrcConversation conversation = _conversation as IrcConversation;
			bool isCommand = text.Length > 1 && text[0] == '/' && text[1] != '/';
			
			if (isCommand)
			{
				string error = null;
				if (!CommandInputHandlerUtility.HandleCommand (this, conversation.Session, conversation, text, out error))
					_message_display.AddError (error);
			}
			else
			{
				//a normal text message
				if (text[0] == '/')
					text = text.Substring (1); //strip the escaped command
				
				conversation.SendMessage (text);
				
				IEnumerable<IMessageChunk> chunks = IrcProtocolHelper.ParseMessage (text);
				IMessage msg = new Message (MessageFlag.Message, Conversation.Session.Account, null);
				msg.Chunks.AddRange (chunks);
				
				_message_display.AddMessage (msg);
			}
		}
		
		private void ChannelContactInitialJoin (object sender, ChannelContactEventArgs args)
		{
			UpdateTotalContacts ();
		}
		
		private void ChannelContactJoin (object sender, ChannelContactEventArgs args)
		{
			if (args.Contact.DisplayName.ToLower().CompareTo(Session.Account.DisplayName.ToLower()) == 0)
			{
				_personalImage.Pixbuf = IconUtility.GetIcon ("galaxium-status-online", IconSizes.Small);
				SwitchTo();
			}
			
			UpdateTotalContacts();
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void ChannelContactPart (object sender, ChannelContactEventArgs args)
		{
			if (args.Contact.DisplayName.ToLower().CompareTo(Session.Account.DisplayName.ToLower()) == 0)
			{
				_contactList.Clear();
				_conversation.ContactCollection.Clear();
				_personalImage.Pixbuf = IconUtility.GetIcon ("galaxium-status-offline", IconSizes.Small);
				SwitchTo();
			}
			
			UpdateTotalContacts ();
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void ChannelContactQuit (object sender, ChannelContactMessageEventArgs args)
		{
			UpdateTotalContacts();
			SwitchTo();
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void ChannelContactKick (object sender, ChannelContactActionEventArgs args)
		{
			if (args.Contact.DisplayName.ToLower().CompareTo(Session.Account.DisplayName.ToLower()) == 0)
			{
				_contactList.Clear();
				_conversation.ContactCollection.Clear();
				_personalImage.Pixbuf = IconUtility.GetIcon ("galaxium-status-offline", IconSizes.Small);
				SwitchTo();
			}
			
			UpdateTotalContacts();
			SwitchTo();
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void ChannelContactKill (object sender, ChannelContactActionEventArgs args)
		{
			UpdateTotalContacts ();
			SwitchTo();
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void ChannelContactModeChanged (object sender, ChannelContactModeEventArgs args)
		{
			_contactList.QueueDraw();
			
			SwitchTo();
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void ChannelAutoJoinChanged (object sender, EntityChangeEventArgs<bool> args)
		{
			SwitchTo();
		}
		
		private void ChannelPersistentChanged (object sender, EntityChangeEventArgs<bool> args)
		{
			SwitchTo();
		}
		
		private void ChannelModeChanged (object sender, ChannelModeEventArgs args)
		{
			GenerateChannelLabel (args.Channel.Topic);
			SwitchTo();
		}
		
		private void SessionContactChanged (object sender, ContactEventArgs args)
		{
			if (Conversation.IsPrivateConversation)
				return;
		}
		
		private void SessionContactPresenceChanged (object sender, EntityChangeEventArgs<IPresence> args)
		{
			if (Conversation.IsPrivateConversation)
				return;
		}
		
		private void SessionContactDisplayNameChanged (object sender, EntityChangeEventArgs<string> args)
		{
			if (Conversation.IsPrivateConversation)
				return;
			
			_contactList.QueueDraw();
			
			SwitchTo();
			
			if (_window.CurrentWidget != this || !_window.Active)
				_window.GenerateAlert ();
			
			EmitBecomeActive ();
		}
		
		private void ChannelBanned (object sender, object arg)
		{
			_message_display.AddEvent ("You are banned from this channel. Unable to join!");
		}
		
		private void ChannelInviteOnly (object sender, object arg)
		{
			_message_display.AddEvent ("You have not been invited to this channel. Unable to join!");
		}
		
		public void SendFile ()
		{
			/*FileChooserDialog dialog = new FileChooserDialog("Select File", null, FileChooserAction.Open, Stock.Cancel, ResponseType.Cancel, Stock.Apply, ResponseType.Accept);
			dialog.SetCurrentFolder(Environment.GetFolderPath(Environment.SpecialFolder.Personal));
			
			int result = dialog.Run ();
			
			Log.Debug ("Result: "+result);
			
			if (result == (int)ResponseType.Accept)
			{
				SendFile(dialog.Filename);
			}
			
			dialog.Destroy();*/
		}
		
		public override void SendFile (string filename)
		{
			/*if (Conversation.PrimaryContact == null)
			{
				Anculus.Core.Log.Warn ("There is no primary contact in this conversation to send files to!");
				return;
			}
			
			// Check to make sure that the file exists.
			if (File.Exists (filename))
			{
				JabberFileTransfer transfer = new JabberFileTransfer (Conversation.Session, Conversation.PrimaryContact, filename);
				
				(Conversation.Session as JabberSession).EmitTransferInvitationSent (new FileTransferEventArgs (transfer));
			}
			else
			{
				string error = "The URI of the file you dropped is invalid!\n"+filename;
				MessageDialog errordialog = new MessageDialog (null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, error);
				errordialog.Run ();
				errordialog.Destroy ();
			}*/
		}
		
		internal void ShowActivityPopup (Widget position)
		{
			//HTMLPopup popup = new HTMLPopup (position, (_conversation as JabberConversation).ActivityPageUrl);
			//popup.ShowAll ();
		}
		
		public override void LoadFont ()
		{
			IConfigurationSection config = Configuration.MessageDisplay.Section[_conversation.Session.Account.UniqueIdentifier]["Font"];
			
			_messageEntry.Family = config.GetString (Configuration.MessageDisplay.Family.Name, Configuration.MessageDisplay.Family.Default);
			_messageEntry.Size = config.GetDouble (Configuration.MessageDisplay.Size.Name, Configuration.MessageDisplay.Size.Default);
			_messageEntry.ColorInt = config.GetInt (Configuration.MessageDisplay.Color.Name, Configuration.MessageDisplay.Color.Default);
			_messageEntry.Bold = config.GetBool (Configuration.MessageDisplay.Bold.Name, Configuration.MessageDisplay.Bold.Default);
			_messageEntry.Italic = config.GetBool (Configuration.MessageDisplay.Italic.Name, Configuration.MessageDisplay.Italic.Default);
			_messageEntry.Underline = config.GetBool (Configuration.MessageDisplay.Underline.Name, Configuration.MessageDisplay.Underline.Default);
			_messageEntry.Strikethrough = config.GetBool (Configuration.MessageDisplay.Strikethrough.Name, Configuration.MessageDisplay.Strikethrough.Default);
			
			MenuUtility.FillToolBar ("/Galaxium/Gui/IRC/ContainerWindow/InputToolbar", new DefaultExtensionContext (this), _entryToolbar);
		}
		
		public override void SaveFont ()
		{
			IConfigurationSection config = Configuration.MessageDisplay.Section[_conversation.Session.Account.UniqueIdentifier]["Font"];

			config.SetString (Configuration.MessageDisplay.Family.Name, _messageEntry.Family);
			config.SetDouble (Configuration.MessageDisplay.Size.Name, _messageEntry.Size);
			config.SetInt (Configuration.MessageDisplay.Color.Name, _messageEntry.ColorInt);
			config.SetBool (Configuration.MessageDisplay.Bold.Name, _messageEntry.Bold);
			config.SetBool (Configuration.MessageDisplay.Italic.Name, _messageEntry.Italic);
			config.SetBool (Configuration.MessageDisplay.Underline.Name, _messageEntry.Underline);
			config.SetBool (Configuration.MessageDisplay.Strikethrough.Name, _messageEntry.Strikethrough);
			
			if (_fontChangedHandlers[_conversation.Session] != null)
				_fontChangedHandlers[_conversation.Session] (this, EventArgs.Empty);
		}
	}
}
